package com.xqboss.apps.service.order.type.impl;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.BooleanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xqboss.apps.constant.Config;
import com.xqboss.apps.constant.RedisKey;
import com.xqboss.apps.domain.box.Box;
import com.xqboss.apps.domain.box.BoxNft;
import com.xqboss.apps.domain.box.BoxPriorityBuy;
import com.xqboss.apps.domain.nft.NftNumber;
import com.xqboss.apps.domain.order.OrderItem;
import com.xqboss.apps.domain.order.OrderMain;
import com.xqboss.apps.dto.order.CreateOrderDto;
import com.xqboss.apps.enums.box.BoxSequenceEnum;
import com.xqboss.apps.enums.order.OrderTypeEnum;
import com.xqboss.apps.service.box.BoxPriorityBuyService;
import com.xqboss.apps.service.box.BoxService;
import com.xqboss.apps.service.nft.NftNumberService;
import com.xqboss.apps.service.order.OrderItemService;
import com.xqboss.apps.service.order.OrderService;
import com.xqboss.apps.service.order.type.OrderTypeExecutor;
import com.xqboss.apps.service.user.UserBoxService;
import com.xqboss.common.core.redis.RedisCache;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.common.utils.SecurityUtils;
import com.xqboss.common.utils.StringUtils;
import com.xqboss.system.service.ISysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *     盒子订单执行器
 * </p>
 * @author Trent
 * @date 2023/8/23
 */
@Slf4j
@Component(value = OrderTypeEnum.Code.Box_NFT)
public class BoxOrderTypeExecutor extends OrderTypeExecutor {

    @Autowired
    private BoxService boxService;

    @Autowired
    @Lazy
    private OrderService orderService;

    @Autowired
    private OrderItemService orderItemService;

    @Autowired
    private NftNumberService nftNumberService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISysConfigService sysConfigService;

    @Autowired
    private UserBoxService userBoxService;

    @Autowired
    private BoxPriorityBuyService boxPriorityBuyService;

    @Override
    protected OrderMain preValidate(CreateOrderDto params) {
        // 藏品存在高并发问题， 需要对用户、商品进行缓存，防止频繁读写数据库
        // 1. 获取箱子信息
        Box box = redisCache.getCacheObject(RedisKey.KEY_CACHE_BOX + params.getSourceId());
        if(Objects.isNull(box)){
            box = boxService.getById(params.getSourceId());
            redisCache.setCacheObject(RedisKey.KEY_CACHE_BOX + params.getSourceId(), box, 5, TimeUnit.MINUTES);
        }
        if(Objects.isNull(box)){
            throw new ServiceException("商品不存在！");
        }
        if(box.getStock() <= 0){
            if(box.getLockStock() <= 0){
                throw new ServiceException("已售罄！");
            }
        }
        if(box.getSaleable() == 0){
            // 未上架
            throw new ServiceException("商品不存在！");
        }
        // 售卖时间判断
        LocalDateTime now = LocalDateTime.now();
        BoxPriorityBuy boxPriorityBuy = boxPriorityBuyService.getBoxPriorityBuy(SecurityUtils.getUserId(), params.getSourceId());
        // 优先购标识
        boolean isBoxPriorityBuy = false;
        if(boxPriorityBuy != null){
            if(now.isBefore(boxPriorityBuy.getPublishTimeStart())){
                throw new ServiceException("商品未开售！");
            }
            if(now.isAfter(boxPriorityBuy.getPublishTimeEnd())){
                // 如果时间是优先购之后，则按照正常商品的开售停售时间走
                if(now.isBefore(box.getPublishTimeStart())){
                    throw new ServiceException("商品未开售！");
                }
                if(Objects.nonNull(box.getPublishTimeEnd())){
                    if(now.isAfter(box.getPublishTimeEnd())){
                        throw new ServiceException("商品已停售！");
                    }
                }
            }else{
                isBoxPriorityBuy = true;
            }
        }else{
            if(now.isBefore(box.getPublishTimeStart())){
                throw new ServiceException("商品未开售！");
            }
            if(Objects.nonNull(box.getPublishTimeEnd())){
                if(now.isAfter(box.getPublishTimeEnd())){
                    throw new ServiceException("商品已停售！");
                }
            }
        }

        // 2. 判断是否限购
        // 优先购限购判断
        if(isBoxPriorityBuy){
            if(orderService.getSpuOrderCount(SecurityUtils.getUserId(), params.getSourceId(), true) + params.getQuantity() > boxPriorityBuy.getLimitStock()){
                throw new ServiceException(StringUtils.format("每个用户只能购买{}件商品", boxPriorityBuy.getLimitStock()));
            }
        }else{
            if(Objects.nonNull(box.getLimitStock())){
                // 获取用户下单该盒子数量
                if(orderService.getSpuOrderCount(SecurityUtils.getUserId(), params.getSourceId(), false) + params.getQuantity() > box.getLimitStock()){
                    throw new ServiceException(StringUtils.format("每个用户只能购买{}件商品", box.getLimitStock()));
                }
            }

            // 判断单次限购
            if(Objects.nonNull(box.getOnceLimitStock())){
                if(params.getQuantity() > box.getOnceLimitStock()){
                    throw new ServiceException(StringUtils.format("单次只能购买{}件商品", box.getOnceLimitStock()));
                }
            }
        }


        // 3. 判断订单金额是否正常
        BigDecimal orderPrice = box.getPrice().multiply(new BigDecimal(String.valueOf(params.getQuantity())));
        if(params.getPrice().compareTo(orderPrice) != 0){
            // 金额不一致
            throw new ServiceException("商品价格已修改，请刷新重试！");
        }

        OrderMain order = buildOrder(params);
        order.setIsPriority(isBoxPriorityBuy);
        order.setPaymentAmount(orderPrice);
        order.setTotalAmount(orderPrice);

        order.setAutoCancelTime(LocalDateTime.now().plusMinutes(sysConfigService.getValue(Config.Sys.Order.订单超时时间)));

        OrderItem orderItem = buildOrderItem(order, params);
        order.setOrderItems(Collections.singletonList(orderItem));
        orderItem.setSpuId(box.getId());
        orderItem.setSpuName(box.getName());
        orderItem.setSkuImg(box.getHeadimg());
        orderItem.setSkuPrice(box.getPrice());
        orderItem.setPublishName(box.getPublishName());
        orderItem.setSkuCnt(params.getQuantity());
        orderItem.setTotalAmount(orderPrice);
        orderItem.setPaymentAmount(orderPrice);

        return order;
    }

    @Override
    protected void lockStock(Long sourceId, Integer quantity) {
        // 藏品存在高并发问题， 需要对库存进行缓存，防止频繁读写数据库
        boxService.lockStock(sourceId, quantity);
    }

    @Override
    public void orderPayedPost(OrderMain order) {
        // 藏品盒子
        OrderItem orderItem = orderItemService.getOne(new LambdaQueryWrapper<OrderItem>()
                .eq(OrderItem::getOrderMainId, order.getId()));
        // 获取盒子映射的NFT信息
        Box box = boxService.getById(orderItem.getSpuId());

        // 扣除盒子库存
        Map<BoxNft, Integer> boxNftDispatch = boxService.takeLockStore(box.getId(), orderItem.getSkuCnt(), null);
        orderService.complete(order.getId(), null);
        if(BooleanUtil.isTrue(box.getAutoOpen())){
            // 如果是自动开箱
            // 分配NFT
            for (BoxNft boxNft : boxNftDispatch.keySet()) {
                nftNumberService.distributeNftNumber(boxNft.getNftId(), orderItem, boxNftDispatch.get(boxNft), box.getSequence() == BoxSequenceEnum.RANDOM);
            }
        }else{
            // 如果不是自动开箱， 则打开装备并存入用户箱子列表
            List<Pair<BoxNft, List<NftNumber>>> nftList = new ArrayList<>();
            for (BoxNft boxNft : boxNftDispatch.keySet()) {
                List<NftNumber> nftNumbers = nftNumberService.distributeBox(orderItem.getUserId(), orderItem.getOrderMainId(), boxNft.getNftId(), boxNftDispatch.get(boxNft), box.getSequence() == BoxSequenceEnum.RANDOM);
                nftList.add(new Pair<>(boxNft, nftNumbers));
            }
            userBoxService.addUserBox(orderItem.getUserId(), orderItem.getOrderMainId(), orderItem.getId(), box, nftList, orderItem.getSkuCnt(), LocalDateTime.now());
        }
    }

    @Override
    public void orderCancelPost(OrderMain order) {
        super.orderCancelPost(order);
        // 回退库存
        OrderItem orderItem = orderItemService.getOne(new LambdaQueryWrapper<OrderItem>()
                .eq(OrderItem::getOrderMainId, order.getId()));
        boxService.unlockStock(orderItem.getSpuId(), orderItem.getSkuCnt());
    }
}
